import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;

/*
 * TCSS 422 Process Control Simulator
 * Group Money: Al McKenzie, Michael Pitts, Taylor Zielske
 */

/**
 * Represents an instance of Shared memory with a mutex control and waiting queue.
 * 
 * @author Michael Pitts
 * @author Taylor Zielske
 * @version Dec 8, 2011
 */
public class Shared {
	
	/** Gives access to the scheduler. */
	private final Scheduler my_scheduler;
	
	/** The data stored in the Shared space. */
	private int my_data;
	
	/** The Processes waiting to read or edit the shared data. */
	private final Queue<Process> my_waiting_process;
	
	/** Processes waiting for another process to finish working shared data. */
	@SuppressWarnings("rawtypes") // can be any shared Process class
	private final Map<Class, Queue<Process>> my_waiting_signalers;
	
	/** The Process currently owning the Shared data.  May be null (no owner).*/
	private Process my_current_owner;
	
	/**
	 * Creates a new Shared memory object with associated system calls.
	 * @param the_scheduler is the scheduler for this simulation.
	 */
	public Shared(final Scheduler the_scheduler) {
		my_scheduler = the_scheduler;
		my_waiting_process = new ArrayDeque<Process>();
		my_current_owner= null;
		my_waiting_signalers = getSignalMap();
	}
	
	/**
	 * @return a map initialized to store Processes that have signaled they are 
	 * waiting for Shared data.
	 */
	@SuppressWarnings("rawtypes") // can be any Shared Process class
	private Map<Class, Queue<Process>> getSignalMap() {
		Map <Class, Queue<Process>> map = new HashMap<Class, Queue<Process>>();
		map.put(Producer.class, new ArrayDeque<Process>());
		map.put(Consumer.class, new ArrayDeque<Process>());
		return map;
	}

	/**
	 * Attempts to down (control) the semaphore.  If the semaphore is not available
	 * the Process is blocked and added to the Semaphore's waiting queue.
	 * @param the_process is the process trying to down the semaphore.
	 * @author Taylor Zielske
	 */
	public synchronized void semaphore_down(final Process the_process){
		Main.out().println("Process " + the_process + " attempting to down the semaphore");
		if (my_current_owner == null) {
			my_current_owner = the_process;
			Main.out().println("Process " + the_process + " downed the semaphore");
		} else {
			the_process.setStatus(Status.BLOCKED);
			my_waiting_process.add(the_process);
			my_scheduler.blockProcess(the_process);
			Main.out().println("Process " + my_current_owner + 
					" owns the semaphone so process " + the_process + 
					" is blocked");
		}
	}
	
	/**
	 * Releases ownership of the semaphore by the given process.
	 * @param the_process the process giving up ownership of the semaphore.
	 * @author Taylor Zielske
	 */
	public synchronized void semaphore_up(final Process the_process){
		if (my_current_owner != null && my_current_owner.equals(the_process)) {
			removeCurrentOwner(); // caller is the owner
		} else { // safety down to ensure all clear, not the owner
			my_waiting_process.remove(the_process);
			my_waiting_signalers.remove(the_process);
		}
	}
	
	/**
	 * Removes the current owner and re sets the owner appropriately.
	 */
	private void removeCurrentOwner() {
		final Process old_owner = my_current_owner;
		 if (!my_waiting_signalers.get(getReverseType(old_owner)).isEmpty()) { 
			 // blocked waiting on another
			reSetOwner(my_waiting_signalers.get(getReverseType(old_owner)).remove());
		} else if (!my_waiting_process.isEmpty()) { // blocked on general use
			reSetOwner(my_waiting_process.remove());
		} else { // nobody waiting
			my_current_owner = null;
			Main.out().println("Process " + old_owner + " semaphore up");
		}
	}

	/**
	 * Changes the owner of the semaphore atomically.
	 * @param the_process is the new owner of the semaphore.
	 */
	private void reSetOwner(final Process the_process) {
		my_current_owner = the_process;
		my_current_owner.setStatus(Status.WAITING);
		my_scheduler.unBlockProcess(my_current_owner);
		Main.out().println("Changing semaphore owner to " + 
		my_current_owner + ", semaphore is still down.");
		Main.out().println("Process " + my_current_owner + " is unblocked.");
	}

	/**
	 * Attempts to read from the shared memory.
	 * @return the data in shared memory.
	 * @author Taylor Zielske
	 */
	public int memory_read(){
		return my_data;
	}
	
	/**
	 * Writes to shared memory.
	 * @param the_data is the data to write to shared memory.
	 * @author Taylor Zielske
	 */
	public void memory_write(final int the_data){
		my_data = the_data;
	}

	/**
	 * A Process is indicating that it is waiting for another Consumer / Producer
	 * to work on the Shared data.
	 * @param the_process is the Process that is waiting.
	 */
	public void signal_wait(final Process the_process) {
		my_waiting_signalers.get(the_process.getClass()).add(the_process);
		the_process.setStatus(Status.BLOCKED);
		my_scheduler.blockProcess(the_process);
		Main.out().println(the_process + " is waiting for another process to " +
				"produce or consume.");
	}

	/**
	 * Gets the class type indicating the type of process that would wait on the give 
	 * type.
	 * @param the_process is the process that is "finished".
	 * @return the class type indicating the type of Process that would wait on 
	 * the given Process.
	 */
	@SuppressWarnings("rawtypes") // could return any Shared type
	private Class getReverseType(final Process the_process) {
		if (Producer.class.isInstance(the_process)) {
			return Consumer.class;
		} else {
			return Producer.class;
		}
	}
}
